using System.Text.RegularExpressions;
using static M3U8Downloader;

partial class Program
{
    private const string TITLE = "M3U8下载器 v1.2 (2025-10-2)";
    private const string M3U8_URL = "M3U8 URL";
    private const string M3U8_URL_QUESTION = $"请输入{M3U8_URL}: ";
    private const string VIDEO_FORMAT = "输出格式";
    private const string VIDEO_FORMAT_QUESTION = $"请选择{VIDEO_FORMAT}(1=M3U8+TS文件，2=MP4，默认1): ";
    private const string OUTPUT_PATH = "保存目录";
    private const string OUTPUT_PATH_QUESTION = $"请输入{OUTPUT_PATH} (例如: d:\\movies): ";
    private const string VIDEO_FILE = "保存文件名";
    private const string VIDEO_FILE_QUESTION = $"请输入{VIDEO_FILE}(例如: d:\\movies\\output.mp4): ";
    private const string THREADS = "下载线程数";
    private const string THREADS_QUESTION = "下载线程数 (1-32, 默认16)：";
    private const string OVERWRITE = "覆盖文件";
    private const string OVERWRITE_QUESTION = "是否覆盖已存在文件 (Y/N，默认N): ";
    private const string SKIPADVERT = "跳过广告切片";
    private const string SKIPADVERT_QUESTION = $"是否{SKIPADVERT} (实验性功能) (Y/N，默认Y): ";
    private const string SKIPOPENING = "跳过片头";
    private const string SKIPOPENING_QUESTION = $"{SKIPOPENING} (格式：分:秒，默认00:00不跳过): ";

    //m3u8 url
    private static string? url;
    private static M3U8Downloader.VideoFileFormat OutputFormat = M3U8Downloader.VideoFileFormat.M3U8AndTS;
    private static string? OutputPath;
    private static int MaxThreads = 16, SkipOpening = 0;
    private static bool OverWrite, SkipAdvert;

    private static Decryptor? decryptor = null;
    private static readonly object? _locker = new ();
    private static int LogTop = Console.WindowHeight - 8;
    public static void ConsoleMessage(string text)
    {
        lock (_locker)
        {
            if (LogTop >= Console.WindowHeight - 4)
                Console.MoveBufferArea(2, Console.WindowHeight - 6, Console.WindowWidth - 4, 3, 2, Console.WindowHeight - 7);
            else
                Interlocked.Increment(ref LogTop);
            Console.SetCursorPosition(2, LogTop);
            if (text.Contains('\n'))
                text = text[..text.IndexOf('\n')];
            int len = text.Length + CNWordCount(text, text.Length);
            Console.Write($"{(len > Console.WindowWidth - 4 ? text[..(Console.WindowWidth - CNWordCount(text, text.Length) - 7)] + "..." : text)}");
        }
    }

    static bool PathOrFilenameIsValid(string input, bool IsFilename)
    {
        if (input.IndexOfAny(Path.GetInvalidPathChars()) > -1)
            return false;
        if (input.Contains(':') && input.IndexOf(':') != input.LastIndexOf(':'))
            return false;
        if (IsFilename && Path.GetFileName(input).IndexOfAny(Path.GetInvalidFileNameChars()) > -1)
            return false;
        return true;
    }

    //计算字符串中包括的中文字数量
    static int CNWordCount(string s, int len)
    {
        int count = 0;
        for (int i = 0; i < len; i++)
        {
            if (s[i] > 255) count++;
        }
        return count;
    }

    static void ShowContent()
    {
        Console.Clear();
        Console.ForegroundColor = ConsoleColor.White;

        int i, width = Console.WindowWidth, height = Console.WindowHeight;
        //输出标题
        Console.SetCursorPosition((width - TITLE.Length) / 2, 1);
        Console.Write(TITLE);
        //输出URL
        Console.SetCursorPosition(2, 3);
        string s = $"{M3U8_URL} │ {url}";
        Console.Write($"{(s.Length > width - 4 ? s[..(width - 7)] + "..." : s)}");

        //输出格式和保存目录
        Console.SetCursorPosition(2, 5);
        string strPath = $"{VIDEO_FORMAT} │ {(OutputFormat == M3U8Downloader.VideoFileFormat.M3U8AndTS ? "M3U8" : "MP4 ")} │ "
            + $"{(OutputFormat == M3U8Downloader.VideoFileFormat.M3U8AndTS ? OUTPUT_PATH : VIDEO_FILE)} │ {OutputPath}";
        Console.Write(strPath);
        var p = Console.GetCursorPosition();
        if (p.Left > width - 2 || p.Top > 5)
        {
            Console.SetCursorPosition(width - 5, 5);
            Console.Write("...");
        }

        //输出线程数等
        Console.SetCursorPosition(2, 7);
        s = $"{THREADS} │ {MaxThreads} │ {OVERWRITE} │ {(OverWrite ? "Y" : "N")} │ {SKIPADVERT} │ {(SkipAdvert ? "Y" : "N")} │ {SKIPOPENING} │ {(SkipOpening/60):00}:{(SkipOpening%60):00} │ ";
        Console.Write(s);

        //绘制表格线
        for (i = 0; i < height; i++)
        {
            Console.SetCursorPosition(0, i);
            Console.Write("│");
            Console.SetCursorPosition(width - 2, i);
            Console.Write("│");
        }

        Console.SetCursorPosition(0, 0);
        Console.Write("┌".PadRight(width - 2, '─') + "┐\n\n" //0
                    + "├".PadRight(width - 2, '─') + "┤\n\n" //2
                    + "├".PadRight(width - 2, '─') + "┤\n\n" //4
                    + "├".PadRight(width - 2, '─') + "┤\n\n" //6
                    + "├".PadRight(width - 2, '─') + "┤");   //8

        Console.SetCursorPosition(0, height - 8);
        Console.Write("├".PadRight(width - 2, '─') + "┤");
        Console.SetCursorPosition(0, height - 3);
        Console.Write("├".PadRight(width - 2, '─') + "┤");

        Console.SetCursorPosition(0, height - 1);
        Console.Write("└".PadRight(width - 2, '─') + "┘");

        Console.SetCursorPosition(M3U8_URL.Length + 3, 2);
        Console.Write("┬");
        Console.SetCursorPosition(M3U8_URL.Length + 3, 4);
        Console.WriteLine("┴");

        i = 1;
        int times = 0;//修正制表符位置（windows多插了一个空格）
        while (strPath.IndexOf('│', i) > -1)
        {
            i = strPath.IndexOf('│', i);
            //1个中文字符占两个英文字符宽度
            Console.SetCursorPosition(i + 2 + CNWordCount(strPath, i) - times, 4);
            Console.Write("┬");
            Console.SetCursorPosition(i + 2 + CNWordCount(strPath, i) - times, 6);
            Console.Write("┴");
            i += 2;
            times++;
        }
        Console.SetCursorPosition(M3U8_URL.Length + 3, 4);
        Console.Write("┼");

        i = 1;
        times = 0;//修正制表符位置（windows多插了一个空格）
        while (s.IndexOf('│', i) > -1)
        {
            i = s.IndexOf('│', i);
            //1个中文字符占两个英文字符宽度
            Console.SetCursorPosition(i + 2 + CNWordCount(s, i) - times, 6);
            Console.Write("┬");
            Console.SetCursorPosition(i + 2 + CNWordCount(s, i) - times, 8);
            Console.Write("┴");
            i += 2;
            times++;
        }
        Console.SetCursorPosition(18, 6);
        Console.Write("┼");
        Console.SetCursorPosition(29, 6);
        Console.Write("┼");

        Console.SetCursorPosition(2, height - 2);
        Console.Write("Source code: https://gitee.com/kacarton/M3U8Downloader");
    }

    public static void ShowProgress(ref List<TsSegment> segments)
    {
        int width = Console.WindowWidth - 4;
        int startX = 2, startY = 9, endY = Console.WindowHeight - 9;
        int Points = (endY - startY + 1) * width;
        double ratio = 1D;
        if (segments.Count < Points)
            Points = segments.Count;
        else
            ratio = 1D * segments.Count / Points;
        Console.SetCursorPosition(2, 9);
        for (int i = 0; i < Points; i++)
        {
            Console.Write('.');
            startX++;
            if (startX >= width + 2)
            {
                startX = 2;
                startY++;
                Console.SetCursorPosition(startX, startY);
            }
        }

        int completed = segments.Where(s => s.download).Count();
        if (completed == segments.Count) return;

        DateTime startTime = DateTime.Now;
        bool first = true;

        var _segments = segments;
        
        Task.Run(async () =>
        {
            while (true)
            {
                while (!first && completed == _segments.Where(s => s.download).Count() && _segments.Where(s => s.download).Count() <= _segments.Count)
                {
                    Thread.Sleep(1000);
                    ShowElapsed(startTime, (double)(completed) / _segments.Count);
                }
                completed = _segments.Where(s => s.download).Count();
                first = false;

                lock (_locker)
                {
                label_paint:
                    startX = 2; startY = 9;
                    for (int i = 0; i < Points; i++)
                    {
                        int cnt = 0, j = (int)(i * ratio), 
                            m = (int)Math.Min(_segments.Count, Math.Ceiling((i + 1) * ratio));
                        for (int n = j; n < m; n++)
                        {
                            if (_segments[n].download)
                                cnt++;
                        }
                        Console.SetCursorPosition(startX, startY);
                        if (cnt >= m-j)
                        {
                            Console.ForegroundColor = ConsoleColor.DarkGreen;
                            Console.Write('*');
                            Console.ForegroundColor = ConsoleColor.White;
                        }
                        else if (cnt > 0)
                        {
                            Console.ForegroundColor = ConsoleColor.DarkYellow;
                            Console.Write('*');
                            Console.ForegroundColor = ConsoleColor.White;
                        }
                        startX++;
                        if (startX >= width + 2)
                        {
                            startX = 2;
                            startY++;
                        }
                    }
                    ShowElapsed(startTime, (double)(completed) / _segments.Count);
                    if (completed == _segments.Count)
                    {
                        ShowElapsed(startTime, 1);
                        if (first)
                            break;
                        else
                        {
                            first = true;
                            goto label_paint;
                        }
                    }
                }
            }
            
        }).Start();
    }

    static void ShowElapsed(DateTime start, double percent)
    {
        TimeSpan elapsed = DateTime.Now - start;
        string s = "   用时：" + (elapsed.TotalHours >= 1 ? $"{(int)elapsed.TotalHours}:{(int)elapsed.Minutes}:{elapsed.Seconds}" 
            : elapsed.TotalMinutes >= 1 ? $"{(int)elapsed.TotalMinutes}分{elapsed.Seconds}秒" 
            : elapsed.Seconds + "秒")
            + "  进度：" + (percent>=1 ? "100%" : $"{percent*100:0.##}%");
        Console.SetCursorPosition(Console.WindowWidth - 2 - s.Length - CNWordCount(s, s.Length), Console.WindowHeight - 2);
        Console.Write(s);
    }

    [GeneratedRegex(@"^https?:\/\/.+\.m3u8(?:\?.*)?$", RegexOptions.IgnoreCase)]
    private static partial Regex UrlRegex();

    [GeneratedRegex(@"^[0-5]?\d:[0-5]?\d$", RegexOptions.IgnoreCase)]
    private static partial Regex TimeRegex();


    static async Task Main(string[] args)
    {
        Console.BufferHeight = Console.WindowHeight;
        Console.WriteLine(TITLE);
        #region 输入参数
        //m3u8网址
        if (args.Length > 0 && UrlRegex().IsMatch(args[0]))
        {
            url = args[0];
            Console.WriteLine($"{M3U8_URL}: " + url);
        }
        else
        {
    loop_url:
            Console.Write(M3U8_URL_QUESTION);
            url = Console.ReadLine();
            //检查网址有效性
            if (string.IsNullOrEmpty(url) || !UrlRegex().IsMatch(url))
            {
                Console.WriteLine("URL错误！");
                goto loop_url;
            }
        }

        //视频格式
        Console.Write(VIDEO_FORMAT_QUESTION);
    loop_format:
        var input = Console.ReadKey(true);//输入时不回显
        if (input.Key == ConsoleKey.Escape) return;
        if (input.Key == ConsoleKey.Enter)
        {
            Console.WriteLine('1');
            goto label_path;
        }
        //只能输入1或2
        if (input.Key != ConsoleKey.D1 && input.Key != ConsoleKey.D2)
        {
            //Console.WriteLine("\n输出格式错误！");
            goto loop_format;
        }
        //输出用户选择
        Console.WriteLine(input.KeyChar);
        OutputFormat = (M3U8Downloader.VideoFileFormat)(input.Key - ConsoleKey.D1);

        //文件保存路径
    label_path:
        bool refresh = false;
        do
        {
            if (OutputFormat == M3U8Downloader.VideoFileFormat.M3U8AndTS)
                Console.Write(OUTPUT_PATH_QUESTION);
            else
                Console.Write(VIDEO_FILE_QUESTION);
            OutputPath = Console.ReadLine();
            if (!string.IsNullOrEmpty(OutputPath) && OutputFormat == M3U8Downloader.VideoFileFormat.MP4)
            {
                string mp4 = Path.GetFileName(OutputPath);
                if (string.IsNullOrEmpty(mp4))
                    OutputPath = "";
                else if (string.IsNullOrEmpty(Path.GetExtension(mp4)))
                {
                    refresh = true;
                    OutputPath += ".mp4";
                }
            }
        }
        while (string.IsNullOrEmpty(OutputPath) || !PathOrFilenameIsValid(OutputPath, OutputFormat == M3U8Downloader.VideoFileFormat.MP4));
        
        //更新显示完整路径
        if (refresh || OutputPath != Path.GetFullPath(OutputPath))
        {
            OutputPath = Path.GetFullPath(OutputPath);
            (var left, var top) = Console.GetCursorPosition();
            Console.SetCursorPosition(left, top - 1);
            if (OutputFormat == M3U8Downloader.VideoFileFormat.M3U8AndTS)
                Console.Write(OUTPUT_PATH_QUESTION);
            else
                Console.Write(VIDEO_FILE_QUESTION);
            Console.WriteLine(OutputPath);
        }

        //最大下载线程数
        Console.Write(THREADS_QUESTION);
        string s = "";
        while (true) 
        {
            input = Console.ReadKey(true);
            if (input.Key == ConsoleKey.Escape)
                return;
            //按Backspace时删除前一字符
            if (input.Key == ConsoleKey.Backspace && s.Length > 0)
            {
                Console.Write(input.KeyChar);//光标前移一位
                Console.Write(' ');          //输出空格擦除字符（光标后移）
                Console.Write(input.KeyChar);//光标前移一位
                s = s[..^1];
            }
            //按回车时检查内容有效性
            if (input.Key == ConsoleKey.Enter)
            {
                //使用默认值
                if (string.IsNullOrEmpty(s))
                {
                    Console.Write(MaxThreads);
                    break;
                }
                //解释输入内容
                if (int.TryParse(s, out int i) && i > 0 && i <= 32)
                {
                    MaxThreads = i;
                    break;
                }
            }

            //限输入字符0-9
            if (input.Key >= ConsoleKey.D0 && input.Key <= ConsoleKey.D9)
            {
                Console.Write(input.KeyChar);
                s += input.KeyChar;
            }
        }
        Console.WriteLine();

        //是否覆盖文件
        Console.Write(OVERWRITE_QUESTION);
        s = "";
        do
        {
            input = Console.ReadKey(true);//输入时不回显
            switch (input.Key)
            {
                case ConsoleKey.Escape:
                    return;
                case ConsoleKey.Enter:
                    s = "N";
                    break;
                //只能输入1或2
                case ConsoleKey.Y:
                case ConsoleKey.N:
                    s = input.Key.ToString();
                    break;
            }
        }
        while (s == "");
        //输出用户选择
        Console.WriteLine(s);
        OverWrite = s == "Y";

        //是否跳过广告
        Console.Write(SKIPADVERT_QUESTION);
        s = "";
        do
        {
            input = Console.ReadKey(true);//输入时不回显
            switch (input.Key)
            {
                case ConsoleKey.Escape:
                    return;
                case ConsoleKey.Enter:
                    s = "Y";
                    break;
                //只能输入1或2
                case ConsoleKey.Y:
                case ConsoleKey.N:
                    s = input.Key.ToString();
                    break;
            }
        }
        while (s == "");
        //输出用户选择
        Console.WriteLine(s);
        SkipAdvert = s == "Y";

    //是否跳过片头
    loop_skipopening:
        Console.Write(SKIPOPENING_QUESTION);
        s = Console.ReadLine();
        //检查网址有效性
        if (!string.IsNullOrEmpty(s))
        {
            if (!TimeRegex().IsMatch(s))
            {
                Console.WriteLine("时间格式错误，请按‘分:秒’的格式录入，例如：3:21、10:59");
                goto loop_skipopening;
            }
            SkipOpening = int.Parse(s.Split(':')[0]) * 60 + int.Parse(s.Split(':')[1]);
        }
        #endregion

        try
        {
            Console.CursorVisible = false;
            ShowContent();

            var downloader = new M3U8Downloader(
                url,
                OutputPath,
                OutputFormat,
                OverWrite,
                SkipAdvert,
                MaxThreads,
                SkipOpening
            );
            await downloader.DownloadAsync(ShowProgress);

            ConsoleMessage("下载完成!");
            Thread.Sleep(1000);
        }
        finally
        {
            Console.CursorVisible = true;
        }
    }
}
